/*
 *  Copyright 2012 Iurii Dunko, Iana Potochniak.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.yyminijava.codegeneration;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opu.yyminijava.util.CodeGenerationException;
import static org.opu.yyminijava.codegeneration.ICode.*;
import org.opu.yyminijava.symboltable.SymbolTable;
import org.opu.yyminijava.syntaxtree.*;
import org.opu.yyminijava.util.Stack;
import org.opu.yyminijava.visitor.VisitorHandler;

/**
 *
 * @author Iurii Dunko
 * @author Iana Potochniak
 */
public class CodeGenerationHandler implements VisitorHandler {

    private SymbolTable symbolTable;
    private ClassRepository repository;
    private Map<String, ClassFile> classes;
    private ClassFile currentClass;
    private Method currentMethod;
    private ArrayList<Throwable> problems;
    
    private ExpressionCodegenerationHelper helper;

    private Stack<Integer> ifStack;
    private Stack<Integer> whileStack;

    public CodeGenerationHandler(SymbolTable symbolTable) {
        this.symbolTable = symbolTable;
        helper = new ExpressionCodegenerationHelper(symbolTable);
    }

    @Override
    public void onVisitStart(Rule rule, boolean isAbstractRule) {
        // upper level declaration part (that must work without problems)
        if (rule instanceof CompilationUnit){
            classes = new HashMap<String, ClassFile>();
            problems = new ArrayList<Throwable>();
            ifStack = new Stack<Integer>();
            whileStack = new Stack<Integer>();
        }
        else if (rule instanceof ClassDeclaration){
            ClassDeclaration cd = (ClassDeclaration) rule;
            currentClass = new ClassFile(cd.getName());
        }
        
        else if(rule instanceof MethodDeclaration){
            MethodDeclaration md = (MethodDeclaration) rule;
            currentMethod = new Method(md.getHeader().getDeclarator().getSimpleName());
            currentMethod.initVariables(md.getHeader().getDeclarator().getParameterList());
        }

        // parse each statement for correct work.
        try{
            if (rule instanceof LocalVariableDeclarationStatement){
                List<VariableDeclarator> variables =
                        ((LocalVariableDeclarationStatement) rule)
                        .getDeclaration().getVariableDeclarators().getVariables();

                for (VariableDeclarator vd : variables) {
                    int index = currentMethod.addVariable(vd.getSimpleName());
                    if(vd.getExpression() != null){
                    	helper.parseExpression(vd.getExpression(), currentMethod);
                    	currentMethod.addInstruction(ISTORE, index);
                    }
                }
            }

            else if (rule instanceof Block){
                // we don't need implement block statement, because block will
                // call his statement by it self and we will parse real
                // statement inside the block.
            }

            else if (rule instanceof MethodCallStatement){
                MethodCallStatement m = (MethodCallStatement) rule;
                
                if (m.getExpression() != null){
                	int varIndex = helper.getVarIndex(m.getPostfixExpression(), 
                			currentMethod, m.toString());
                	helper.parseExpression(m.getExpression(), currentMethod);
                	currentMethod.addInstruction(ISTORE, varIndex);
                } else {
                	helper.parsePostfixExpression(m.getPostfixExpression(), currentMethod);
                }
            }

            else if (rule instanceof ReturnStatement){
                ReturnStatement rs = (ReturnStatement) rule;
                
                if (rs.getExpression() != null){
                	helper.parseExpression(rs.getExpression(), currentMethod);
                }
                
                currentMethod.addInstruction(RETURN, null);
            }

            else if (rule instanceof IfElseStatement) {
                // we will check it in parts
            }

            else if (rule instanceof IfStatementPart) {
                Expression exp = ((IfStatementPart) rule).getExpression();
                helper.parseExpression(exp, currentMethod);
                currentMethod.addInstruction(IF_FALSE, null);
                ifStack.push(currentMethod.getCurrentInstructionIndex());
            }

            else if (rule instanceof ElseStatementPart){
                currentMethod.addInstruction(GOTO, null);
                int index = currentMethod.getCurrentInstructionIndex();
                currentMethod.getInstruction(ifStack.pop()).setArg(index + 1);
                ifStack.push(index);
            }

            else if (rule instanceof WhileStatement){
                whileStack.push(currentMethod.getCurrentInstructionIndex());
                Expression exp = ((WhileStatement)rule).getExpression();
                helper.parseExpression(exp, currentMethod);
                currentMethod.addInstruction(IF_FALSE, null);
                whileStack.push(currentMethod.getCurrentInstructionIndex());
            }

            else if (rule instanceof Statement){
                throw new CodeGenerationException("Unsupported statement: " 
                        + rule.getClass().getName() + ": " + rule);
            }

        } catch(Throwable er){
            problems.add(er);
        }

    }

    @Override
    public void onVisitEnd(Rule rule, boolean isAbstractRule) {
        if (rule instanceof CompilationUnit) {
            repository = new ClassRepository(classes, symbolTable);
            List<String> mc = helper.resetStaticMethodCalls();
            for (String string : mc) {
                if (!repository.getMethod(string).isStaticModifier()) {
                    throw new CodeGenerationException(string + " isn't static method");
                }
            }
        } else if (rule instanceof ClassDeclaration) {
            classes.put(currentClass.getName(), currentClass);
        } else if (rule instanceof MethodDeclaration) {
            currentClass.addMethod(currentMethod.getName(), currentMethod);
        }

        else if (rule instanceof ElseStatementPart){
            int index = currentMethod.getCurrentInstructionIndex();
            currentMethod.getInstruction(ifStack.pop()).setArg(index + 1);
        }
        else if (rule instanceof IfElseStatement){
            if (((IfElseStatement)rule).getElseStatementPart() == null){
                int index = currentMethod.getCurrentInstructionIndex();
                currentMethod.getInstruction(ifStack.pop()).setArg(index + 1);
            }
        } 
        else if (rule instanceof WhileStatement) {
            int index = currentMethod.getCurrentInstructionIndex();
            currentMethod.getInstruction(whileStack.pop()).setArg(index + 2);
            currentMethod.addInstruction(GOTO, whileStack.pop() + 1);
        }
    }

    public ClassRepository getRepository() {
        return repository;
    }

    public ArrayList<Throwable> getProblemsStack() {
        @SuppressWarnings("unchecked")
		ArrayList<Throwable> clone = (ArrayList<Throwable>) problems.clone();
        Collections.reverse(clone);
        return clone;
    }
    
}
